package top.codedance.iotp.client.plugins.async.bacnet;

import cd.io.netty.channel.ChannelHandlerContext;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.ClientApplication;
import top.codedance.iotp.common.entity.CommonEntity;
import top.codedance.iotp.common.executer.AsyncAbstractExecuter;
import org.apache.log4j.Logger;
import org.ini4j.Ini;
import org.ini4j.Wini;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BacnetExecuterImpl extends AsyncAbstractExecuter {
    private Logger logger = ClientApplication.getLogger(BacnetExecuterImpl.class);
    private static ExecutorService bacnetPools = Executors.newCachedThreadPool();
    private static Map<Integer, Service> bacnets = new HashMap<>();
    public static volatile Map<Integer, Set<SubscribeParam>> tasks = new HashMap<>();
    private static volatile Map<Integer, Integer> subscriberProcess = new HashMap<>();

    @Override
    public void exec(ChannelHandlerContext channelHandlerContext, CommonEntity commonEntity, String taskId) {
        super.exec(channelHandlerContext, commonEntity, taskId);
        bacnetPools.submit(() -> {
            JSONObject requestBodyJSONObject = getRequestBodyJSONObject();
            Integer instanceId = requestBodyJSONObject.getInteger("instance");
            tryCreateConnect(this, instanceId);
            if(requestBodyJSONObject.containsKey("body")){
                synchronized (BacnetExecuterImpl.class) {
                    JSONArray body = requestBodyJSONObject.getJSONArray("body");
                    Service service = bacnets.get(instanceId);
                    JSONObject item;
                    for (int i = 0; i < body.size(); i++) {
                        item = body.getJSONObject(i);
                        if(item.containsKey("isWrite") && item.getBoolean("isWrite")){
                            service.send(item);
                        }else{
                            if (!tasks.containsKey(instanceId)) {
                                tasks.put(instanceId, new LinkedHashSet<>());
                            }
                            SubscribeParam subscribeParam = new SubscribeParam(item.getString("tag"), getSubscriberProcess(instanceId), item.getIntValue("deviceId"), item.getIntValue("property"), item.getIntValue("datatype"), item.getIntValue("instance"), getClientId(), instanceId);
                            Set<SubscribeParam> subscribeParams = tasks.get(instanceId);
                            boolean flag = false;
                            String s = subscribeParam.md5();
                            for (SubscribeParam sp : subscribeParams){
                                if(s.equals(sp.md5())){
                                    flag = true;
                                    break;
                                }
                            }
                            if(!flag) {
                                subscribeParams.add(subscribeParam);
                            }
                            service.subscribe(subscribeParam);
                        }
                    }
                }
            }
            if(requestBodyJSONObject.containsKey("release") && requestBodyJSONObject.getBoolean("release")){
                synchronized (BacnetExecuterImpl.class) {
                    Set<Map.Entry<Integer, Set<SubscribeParam>>> entries = tasks.entrySet();
                    Set<SubscribeParam> value;
                    SubscribeParam subscribeParam;
                    for (Map.Entry<Integer, Set<SubscribeParam>> entry : entries) {
                        value = entry.getValue();
                        Iterator<SubscribeParam> iterator = value.iterator();
                        List<SubscribeParam> removeItems = new ArrayList<>();
                        while (iterator.hasNext()){
                            subscribeParam = iterator.next();
                            if (subscribeParam.getClientId().equals(getClientId())){
                                try {
                                    removeItems.add(subscribeParam);
                                } catch (Exception e) { }
                            }
                        }
                        for (SubscribeParam item : removeItems){
                            value.remove(item);
                            if(isUnSubscribe(value, item)) {
                                Service service = bacnets.get(instanceId);
                                //取消定阅
                                service.unSubscribe(item);
                            }
                        }
                    }
                }
            }
        });
    }

    private synchronized void tryCreateConnect(AsyncAbstractExecuter executer, Integer instanceId){
        if(!bacnets.containsKey(instanceId)) {
            Service service = null;
            Wini ini = getIniConfig();
            if (ini != null && ini.containsKey("bacnet")) {
                BacNetServiceParam bacNetServiceParam = new BacNetServiceParam();
                String type = "ip";
                Ini.Section section = ini.get("bacnet");
                if (section.containsKey("type." + instanceId)) {
                    type = section.get("type." + instanceId);
                }
                if (type.equals("ip")) {
                    if (section.containsKey("localBindAddress." + instanceId)) {
                        bacNetServiceParam.setLocalBindAddress(section.get("localBindAddress." + instanceId));
                    }
                    if (section.containsKey("networkPrefixLength." + instanceId)) {
                        bacNetServiceParam.setNetworkPrefixLength(Integer.valueOf(section.get("networkPrefixLength." + instanceId)));
                    }
                    if (section.containsKey("subnetAddress." + instanceId)) {
                        bacNetServiceParam.setSubnetAddress(section.get("subnetAddress." + instanceId));
                    }
                    if (section.containsKey("port." + instanceId)) {
                        bacNetServiceParam.setPort(Integer.valueOf(section.get("port." + instanceId)));
                    }
                    service = new BacNetIpService(executer, bacNetServiceParam, instanceId);
                }
                if (type.equals("mstp")) {
                    if (section.containsKey("com." + instanceId)) {
                        bacNetServiceParam.setCom(Integer.valueOf(section.get("com." + instanceId)));
                    }
                    if (section.containsKey("comName." + instanceId)) {
                        bacNetServiceParam.setComName(section.get("comName." + instanceId));
                    }
                    if (section.containsKey("dataBits." + instanceId)) {
                        bacNetServiceParam.setDataBits(Integer.valueOf(section.get("dataBits." + instanceId)));
                    }
                    if (section.containsKey("baudRate." + instanceId)) {
                        bacNetServiceParam.setBaudRate(Integer.valueOf(section.get("baudRate." + instanceId)));
                    }
                    if (section.containsKey("stopBits." + instanceId)) {
                        bacNetServiceParam.setStopBits(Integer.valueOf(section.get("stopBits." + instanceId)));
                    }
                    if (section.containsKey("parity." + instanceId)) {
                        bacNetServiceParam.setParity(Integer.valueOf(section.get("parity." + instanceId)));
                    }
                    service = new BacNetMsTpService(executer, bacNetServiceParam, instanceId);
                }

                try {
                    service.createConnect();
                    bacnets.put(instanceId, service);
                } catch (Exception e) {
                    logger.debug(e.getMessage());
                }
            }
        }
    }

    private int getSubscriberProcess(Integer instance){
        if(!subscriberProcess.containsKey(instance)){
            subscriberProcess.put(instance, 0);
        }
        Integer integer = subscriberProcess.get(instance);
        integer++;
        subscriberProcess.put(instance, integer);
        return integer;
    }

    private boolean isUnSubscribe(Set<SubscribeParam> params, SubscribeParam item){
        boolean flag = true;
        for (SubscribeParam subscribeParam : params){
            if(subscribeParam.getDeviceId() == item.getDeviceId() && subscribeParam.getProperty() == item.getProperty() && subscribeParam.getDatatype() == item.getDatatype() && subscribeParam.getDataInstance() == item.getDataInstance()){
                flag = false;
                break;
            }
        }
        return flag;
    }
}
